// Copyright (c) 2024 CHANGLEI. All rights reserved.

import 'package:charts_grasp/src/chart/axis/axis.dart';
import 'package:charts_grasp/src/chart/axis/category_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_axis.dart';
import 'package:charts_grasp/src/chart/axis/datetime_category_axis.dart';
import 'package:charts_grasp/src/chart/base/chart_base.dart';
import 'package:charts_grasp/src/chart/chart_segment/chart_segment.dart';
import 'package:charts_grasp/src/chart/chart_series/series.dart';
import 'package:charts_grasp/src/chart/chart_series/xy_data_series.dart';
import 'package:charts_grasp/src/chart/common/cartesian_state_properties.dart';
import 'package:charts_grasp/src/chart/common/common.dart';
import 'package:charts_grasp/src/chart/common/data_label.dart';
import 'package:charts_grasp/src/chart/common/marker.dart';
import 'package:charts_grasp/src/chart/common/renderer.dart';
import 'package:charts_grasp/src/chart/common/segment_properties.dart';
import 'package:charts_grasp/src/chart/trendlines/trendlines.dart';
import 'package:charts_grasp/src/chart/utils/helper.dart';
import 'package:charts_grasp/src/common/user_interaction/selection_behavior.dart';
import 'package:charts_grasp/src/common/utils/helper.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' show DateFormat;

class SeriesRendererDetails {
  SeriesRendererDetails(this.renderer);

  final CartesianSeriesRenderer renderer;

  String? seriesName;

  bool? visible;

  bool needsRepaint = true;

  late CgCartesianChart chart;

  late String seriesType;

  bool isRectSeries = false;

  bool isPointSeries = false;

  bool hasSideBySideInfo = false;

  bool isCalculateRegion = false;

  bool needsToCalculateRegion = false;

  final List<List<Offset>> drawControlPoints = <List<Offset>>[];

  final List<List<Offset>> drawLowControlPoints = <List<Offset>>[];

  final List<List<Offset>> drawHighControlPoints = <List<Offset>>[];

  Path? segmentPath;

  List<ChartSegment> segments = <ChartSegment>[];

  CartesianSeries<dynamic, dynamic>? oldSeries;

  late CartesianSeries<dynamic, dynamic> series;

  List<CartesianChartPoint<dynamic>> dataPoints = <CartesianChartPoint<dynamic>>[];

  List<CartesianChartPoint<dynamic>>? visibleDataPoints;

  List<CartesianChartPoint<dynamic>>? oldDataPoints;

  List<int>? oldSelectedIndexes;

  ChartAxisRendererDetails? xAxisDetails;

  ChartAxisRendererDetails? yAxisDetails;

  num? minimumX;

  num? maximumX;

  num? minimumY;

  num? maximumY;

  Map<dynamic, dynamic>? regionalData;

  Color? seriesColor;

  List<dynamic>? xValues;

  late CartesianStateProperties stateProperties;

  late List<Path?> markerShapes;

  late List<Path?> markerShapes2;

  bool isOuterRegion = false;

  bool isIndicator = false;

  num? minDelta;

  late ValueNotifier<int> repaintNotifier;

  bool needAnimateSeriesElements = false;

  bool needsAnimation = false;

  bool reAnimate = false;

  bool calculateRegion = false;

  Animation<double>? seriesAnimation;

  Animation<double>? seriesElementAnimation;

  late AnimationController animationController;

  ChartSeriesController? controller;

  List<TrendlineRenderer> trendlineRenderer = <TrendlineRenderer>[];

  late DataLabelSettingsRenderer dataLabelSettingsRenderer;

  MarkerSettingsRenderer? markerSettingsRenderer;

  bool isSelectionEnable = false;

  SelectionBehaviorRenderer? selectionBehaviorRenderer;

  dynamic selectionBehavior;

  bool isMarkerRenderEvent = false;

  late bool animationCompleted;

  bool hasDataLabelTemplate = false;

  VisibleRange? sideBySideInfo;

  late num rectPosition;

  List<CartesianSeriesRenderer>? oldSeriesRenderers;

  double? maxSize;

  double? minSize;

  int? seriesIndex;

  List<CartesianChartPoint<dynamic>> overallDataPoints = <CartesianChartPoint<dynamic>>[];

  List<StackedValues> stackingValues = <StackedValues>[];

  List<num> percentageValues = <num>[];

  final List<num?> xValueList = <num?>[];

  final List<num?> yValueList = <num?>[];

  final bool isLineType = false;

  List<double>? dashArray;

  bool containsEmptyPoints = false;

  List<CartesianChartPoint<dynamic>?> overAllDataPoints = <CartesianChartPoint<dynamic>?>[];

  void drawSegment(Canvas canvas, ChartSegment segment) {
    final SegmentProperties segmentProperties = SegmentHelper.getSegmentProperties(segment);
    if (SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer).isSelectionEnable == true) {
      final SelectionBehaviorRenderer? selectionBehaviorRenderer =
          SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer).selectionBehaviorRenderer;
      SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!)
          .selectionRenderer
          ?.checkWithSelectionState(segments[segment.currentSegmentIndex!], stateProperties.chart);
    }
    segment.onPaint(canvas);
  }

  void renderSeriesElements(CgCartesianChart chart, Canvas canvas, Animation<double>? animationController) {
    markerShapes = <Path?>[];
    markerShapes2 = <Path?>[];
    final List<CartesianChartPoint<dynamic>> data = dataPoints;
    for (int pointIndex = 0; pointIndex < data.length; pointIndex++) {
      final CartesianChartPoint<dynamic> point = data[pointIndex];
      if (series.markerSettings.isVisible) {
        markerSettingsRenderer?.renderMarker(this, point, animationController, canvas, pointIndex);
      }
    }
  }

  void repaintSeriesElement() {
    repaintNotifier.value++;
  }

  void animationStatusListener(AnimationStatus status) {
    if (status == AnimationStatus.completed) {
      reAnimate = false;
      animationCompleted = true;
      stateProperties.animationCompleteCount++;
      setAnimationStatus(stateProperties);
    } else if (status == AnimationStatus.forward) {
      stateProperties.renderingDetails.animateCompleted = false;
      animationCompleted = false;
    }
  }

  void storeSeriesProperties(CartesianStateProperties stateProperties, int index) {
    this.stateProperties = stateProperties;
    chart = stateProperties.chart;
    isRectSeries = seriesType.contains('column') ||
        (seriesType.contains('bar') && !seriesType.contains('errorbar')) ||
        seriesType == 'histogram';
    regionalData = <dynamic, dynamic>{};
    segmentPath = Path();
    segments = <ChartSegment>[];
    seriesColor = series.color ?? chart.palette[index % chart.palette.length];

    final List<Trendline>? trendlines = series.trendlines;
    if (trendlines != null && chart.tooltipBehavior.enable) {
      for (int j = 0; j < trendlines.length; j++) {
        if (trendlineRenderer[j].isNeedRender) {
          if (trendlineRenderer[j].pointsData != null) {
            for (int k = 0; k < trendlineRenderer[j].pointsData!.length; k++) {
              final CartesianChartPoint<dynamic> trendlinePoint = trendlineRenderer[j].pointsData![k];
              calculateTooltipRegion(
                  trendlinePoint, index, this, stateProperties, trendlines[j], trendlineRenderer[j], j,);
            }
          }
        }
      }
    }
  }

  void setSeriesProperties(SeriesRendererDetails seriesRendererDetails) {
    if (seriesType.contains('column') ||
        seriesType.contains('stackedbar') ||
        seriesType == 'bar' ||
        seriesType == 'histogram' ||
        seriesType == 'waterfall') {
      isRectSeries = true;
      needsToCalculateRegion =
          seriesType.contains('waterfall') || seriesType.contains('stackedcolumn') || seriesType.contains('stackedbar');
    } else if (seriesType == 'scatter' || seriesType == 'bubble') {
      isPointSeries = true;
    }

    hasSideBySideInfo = isRectSeries ||
        (seriesType.contains('candle') ||
            seriesType.contains('hilo') ||
            seriesType.contains('histogram') ||
            seriesType.contains('box'));

    isCalculateRegion = seriesType != 'errorbar' &&
        seriesType != 'boxandwhisker' &&
        ((chart.tooltipBehavior.enable) ||
            (seriesRendererDetails.series.onPointTap != null ||
                seriesRendererDetails.series.onPointDoubleTap != null ||
                seriesRendererDetails.series.onPointLongPress != null));
  }

  void calculateRegionData(CartesianStateProperties stateProperties, SeriesRendererDetails seriesRendererDetails,
      int seriesIndex, CartesianChartPoint<dynamic> point, int pointIndex,
      [VisibleRange? sideBySideInfo, CartesianChartPoint<dynamic>? nextPoint, num? midX, num? midY,]) {
    if (withInRange(seriesRendererDetails.dataPoints[pointIndex].xValue, seriesRendererDetails.xAxisDetails!)) {
      seriesRendererDetails.visibleDataPoints!.add(seriesRendererDetails.dataPoints[pointIndex]);
      seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex =
          seriesRendererDetails.visibleDataPoints!.length - 1;
    }
    chart = stateProperties.chart;
    final Rect rect = calculatePlotOffset(
        stateProperties.chartAxis.axisClipRect, Offset(xAxisDetails!.axis.plotOffset, yAxisDetails!.axis.plotOffset),);

    CartesianChartPoint<dynamic> point;
    if (visible!) {
      point = dataPoints[pointIndex];
      if (point.region == null || seriesRendererDetails.calculateRegion == true || needsToCalculateRegion) {
        if (seriesRendererDetails.calculateRegion == true && dataPoints.length == pointIndex - 1) {
          seriesRendererDetails.calculateRegion = false;
        }

        seriesRendererDetails.sideBySideInfo = hasSideBySideInfo
            ? calculateSideBySideInfo(seriesRendererDetails.renderer, stateProperties)
            : seriesRendererDetails.sideBySideInfo;
        if (isRectSeries) {
          calculateRectSeriesRegion(point, pointIndex, seriesRendererDetails, stateProperties);
        } else if (seriesType == 'errorbar') {
          calculateErrorBarSeriesRegion(point, pointIndex, seriesRendererDetails, stateProperties, rect);
        } else {
          calculatePathSeriesRegion(point, pointIndex, this, stateProperties, rect, series.markerSettings.height,
              series.markerSettings.width, sideBySideInfo, nextPoint, midX, midY,);
        }
      }
      if (isCalculateRegion) {
        calculateTooltipRegion(point, seriesIndex, seriesRendererDetails, stateProperties);
      }
    }
  }

  void calculateTooltipRegionUsingIndex(
      CgCartesianChart chart, int seriesIndex, CartesianChartPoint<dynamic> point, int pointIndex,) {
    if (series.enableTooltip && !point.isGap && !point.isDrop) {
      final List<String> regionData = <String>[];
      String? date;
      final List<dynamic> regionRect = <dynamic>[];
      final dynamic primaryAxisDetails = xAxisDetails;
      if (primaryAxisDetails is DateTimeAxisDetails) {
        final DateTimeAxis axis = primaryAxisDetails.axis as DateTimeAxis;
        final num interval = primaryAxisDetails.visibleRange!.minimum.ceil();
        final num prevInterval = (primaryAxisDetails.visibleLabels.isNotEmpty)
            ? primaryAxisDetails.visibleLabels[primaryAxisDetails.visibleLabels.length - 1].value
            : interval;
        final DateFormat dateFormat = axis.dateFormat ??
            getDateTimeLabelFormat(xAxisDetails!.axisRenderer, interval.toInt(), prevInterval.toInt());
        date = dateFormat.format(DateTime.fromMillisecondsSinceEpoch(point.xValue));
      } else if (primaryAxisDetails is DateTimeCategoryAxisDetails) {
        date = primaryAxisDetails.dateFormat.format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor()));
      }
      xAxisDetails is CategoryAxisDetails
          ? regionData.add(point.x.toString())
          : xAxisDetails is DateTimeAxisDetails || xAxisDetails is DateTimeCategoryAxisDetails
              ? regionData.add(date.toString())
              : regionData
                  .add(getLabelValue(point.xValue, xAxisDetails!.axis, chart.tooltipBehavior.decimalPlaces));
      if (seriesType.contains('range')) {
        regionData.add(getLabelValue(point.high, yAxisDetails!.axis, chart.tooltipBehavior.decimalPlaces));
        regionData.add(getLabelValue(point.low, yAxisDetails!.axis, chart.tooltipBehavior.decimalPlaces));
      } else {
        regionData.add(getLabelValue(point.yValue, yAxisDetails!.axis, chart.tooltipBehavior.decimalPlaces));
      }
      regionData.add(series.name ?? 'series $seriesIndex');
      regionRect.add(point.region);
      regionRect.add(isRectSeries
          ? seriesType == 'column' || seriesType.contains('stackedcolumn')
              ? (point.yValue > 0) == true
                  ? point.region!.topCenter
                  : point.region!.bottomCenter
              : point.region!.topCenter
          : (seriesType == 'rangearea'
              ? Offset(point.markerPoint!.x, (point.markerPoint!.y + point.markerPoint2!.y) / 2)
              : point.region!.center),);
      regionRect.add(point.pointColorMapper);
      regionRect.add(point.bubbleSize);
      if (seriesType.contains('stacked')) {
        regionData.add(point.cumulativeValue.toString());
      }
      regionalData![regionRect] = regionData;
    }
  }

  void calculateAverageModeValue(int pointIndex, int pointLength, CartesianChartPoint<dynamic> currentPoint,
      CartesianChartPoint<dynamic> prevPoint,) {
    final List<dynamic> dataSource = series.dataSource;
    final CartesianChartPoint<dynamic> nextPoint = getPointFromData(
        this,
        pointLength < dataSource.length - 1
            ? dataSource.indexOf(dataSource[pointLength + 1])
            : dataSource.indexOf(dataSource[pointLength]),);
    if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle')) {
      if (pointIndex == 0) {
        if (currentPoint.low == null) {
          pointIndex == dataSource.length - 1 ? currentPoint.low = 0 : currentPoint.low = ((nextPoint.low) ?? 0) / 2;
        }
        if (currentPoint.high == null) {
          pointIndex == dataSource.length - 1 ? currentPoint.high = 0 : currentPoint.high = ((nextPoint.high) ?? 0) / 2;
        }
        if (seriesType == 'hiloopenclose' || seriesType == 'candle') {
          if (currentPoint.open == null) {
            pointIndex == dataSource.length - 1
                ? currentPoint.open = 0
                : currentPoint.open = ((nextPoint.open) ?? 0) / 2;
          }
          if (currentPoint.close == null) {
            pointIndex == dataSource.length - 1
                ? currentPoint.close = 0
                : currentPoint.close = ((nextPoint.close) ?? 0) / 2;
          }
        }
      } else if (pointIndex == dataSource.length - 1) {
        currentPoint.low = currentPoint.low ?? ((prevPoint.low) ?? 0) / 2;
        currentPoint.high = currentPoint.high ?? ((prevPoint.high) ?? 0) / 2;

        if (seriesType == 'hiloopenclose' || seriesType == 'candle') {
          currentPoint.open = currentPoint.open ?? ((prevPoint.open) ?? 0) / 2;
          currentPoint.close = currentPoint.close ?? ((prevPoint.close) ?? 0) / 2;
        }
      } else {
        currentPoint.low = currentPoint.low ?? (((prevPoint.low) ?? 0) + ((nextPoint.low) ?? 0)) / 2;
        currentPoint.high = currentPoint.high ?? (((prevPoint.high) ?? 0) + ((nextPoint.high) ?? 0)) / 2;

        if (seriesType == 'hiloopenclose' || seriesType == 'candle') {
          currentPoint.open = currentPoint.open ?? (((prevPoint.open) ?? 0) + ((nextPoint.open) ?? 0)) / 2;
          currentPoint.close = currentPoint.close ?? (((prevPoint.close) ?? 0) + ((nextPoint.close) ?? 0)) / 2;
        }
      }
    } else {
      if (pointIndex == 0) {
        pointIndex == dataSource.length - 1 ? currentPoint.y = 0 : currentPoint.y = ((nextPoint.y) ?? 0) / 2;
      } else if (pointIndex == dataSource.length - 1) {
        currentPoint.y = ((prevPoint.y) ?? 0) / 2;
      } else {
        currentPoint.y = (((prevPoint.y) ?? 0) + ((nextPoint.y) ?? 0)) / 2;
      }
    }
  }

  void dispose() {
    for (final ChartSegment segment in segments) {
      segment.dispose();
    }

    segments.clear();
  }
}
